package com.tangsm.spring.boot.deferred.result.controller;

import com.tangsm.spring.boot.deferred.result.domain.vo.Result;
import com.tangsm.spring.boot.deferred.result.queue.TaskQueue;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.context.request.async.DeferredResult;

import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

/**
 * DeferredResult + 队列 实现异步请求调用
 * <pre>
 * DeferredResult和Callable一样，DeferredResult也是为了支持异步调用。
 * 两者的主要差异，Sunny觉得主要在DeferredResult需要自己用线程来处理结果setResult，而Callable的话不需要我们来维护一个结果处理线程。
 * 总体来说，Callable的话更为简单，同样的也是因为简单，灵活性不够；
 * 相对地，DeferredResult更为复杂一些，但是又极大的灵活性。在可以用Callable的时候，直接用Callable；
 * 而遇到Callable没法解决的场景的时候，可以尝试使用DeferredResult。这里Sunny将会设计两个DeferredResult使用场景。
 *
 * DeferredResult的流程：
 * 1.浏览器发起异步请求
 * 2.请求到达服务端被挂起
 * 3.向浏览器进行响应，分为两种情况：
 *   3.1.调用DeferredResult.setResult()，请求被唤醒，返回结果
 *   3.2.超时，返回一个你设定的结果
 * 4.浏览得到响应，再次重复1，处理此次响应结果
 *
 * 场景：建立一次连接，让他们等待尽可能长的时间，这样同时如果有新的数据到达服务器，服务器可以直接返回响应，减少所涉及的请求和响应周期的数量。
 * 1. 创建一个持续在随机间隔时间后从任务队列中获取任务的线程
 * 2. 访问controller中的方法，创建一个DeferredResult，设定超时时间和超时返回对象
 * 3. 设定DeferredResult的超时回调方法和完成回调方法
 * 4. 将DeferredResult放入任务中，并将任务放入任务队列
 * 5. 步骤1中的线程获取到任务队列中的任务，并产生一个随机结果返回
 * 6. 场景其实非常简单，接下来我们来看看具体的实现。首先，我们还是来看任务实体类是怎么样的。
 *
 * 长轮询其他实现方案参考：https://blog.csdn.net/qq_44600824/article/details/127085246
 * </pre>
 *
 * @author tangsm
 */
@RestController
@RequestMapping("/deferred/queue")
public class DeferredResultQueueTaskController {
    private static final Logger log = LoggerFactory.getLogger(DeferredResultQueueTaskController.class);

    /** 超时时间 */
    private static final long OUT_OF_TIME = 5000L;

    @Autowired
    private TaskQueue taskQueue;

    @GetMapping(value = "/get")
    public DeferredResult<Result<String>> getResult() {
        log.info("DeferredResult异步请求，开始处理...");

        //建立DeferredResult对象，设置超时时间，以及超时返回超时结果
        DeferredResult<Result<String>> result = new DeferredResult<>(OUT_OF_TIME);

        // 请求超时时调用
        result.onTimeout(() -> {
            log.info("DeferredResult异步请求，调用超时");
        });

        // 请求完成时调用
        result.onCompletion(() -> {
            log.info("DeferredResult异步请求，调用完成");
        });

        //锁
        Lock lock = new ReentrantLock();
        try {
            // 并发，加锁
            lock.lock();
            log.info("DeferredResult异步请求，加入任务");
            // 加入任务
            taskQueue.put(result);
        } catch (Exception e) {
            log.error("DeferredResult异步请求，加入任务异常", e);
            throw new RuntimeException(e);
        } finally {
            lock.unlock();
        }

        log.info("DeferredResult异步请求，完成并退出");
        return result;
    }
}
